<?php
/**
 * @version : 1.0
 * @package : lib/form
 * @todo:  class base FormLayout
*/
class CFormLayout
{
	public $code;
	public $tokens;
	public $error = false;
	public $line = 0;
	public $start_loop_index = 0;
	public $loop_index = 0;
	public $loops = array();
	public $file_input_types = array();
	static $layout_tags = array(
		'BEGIN_LIST' => '<\!\-\-LIST\(',
		'BEGIN_FOR' => '<\!\-\-FOR\(',
		'BEGIN_IF' => '<\!\-\-IF\(',
		'CLOSE_TAG' => '\)\-\->',
		'BEGIN_SWITCH' => '<\!\-\-SWITCH\(',
		'END_LIST' => '<\!\-\-\/LIST\-\->',
		'END_IF' => '<\!\-\-\/IF\-\->',
		'END_FOR' => '<\!\-\-\/FOR\-\->',
		'END_SWITCH' => '<\!\-\-\/SWITCH\-\->',
		'BEGIN_INPUT' => '\<i\:([\w\-]+)\s*([^>]*)\>',
		'END_INPUT' => '<\/i:([\w\-]+)\s*>',
		'ELSE' => '<!--ELSE-->',
		'DEFAULT' => '<!--DEFAULT-->',
		'CASE' => '<\!\-\-CASE\(',
		'LEFT_BRACKET' => '\\\{',
		'RIGHT_BRACKET' => '\\\}',
		'EVAL_VARIABLE' => '\{\{([^\}\r\n]+)\}\}',
		'VARIABLE' => '\{([^\}\r\n]+)\}',
		'BEGIN_PHP' => '\<\?php',
		'END_PHP' => '\?\>',
		'NEW_LINE' => "\r\n",
	);
	static $layout_template_tags = array(
		'METHOD_CALL' => '\-\>([\w]+)\(',
		'ARRAY' => '\[([\w]+)\]',
		'CLASS_ATTRIBUTE' => '\-\>([\w]+)',
		'FUNCTION_CALL' => '([\w\:\$]+)\(',
		'CUSTOM_FIELD' => '\%\%',
		'RIGHT_PARENTHESE' => '\)',
		'RIGHT_BRACE' => '\]',
		'STRING' => '\'([^\']+)\'',
		'PHP_VARIABLE' => '(\$[\w]+)',
		'CLASS_STATIC' => '([\w]+)\:\:',
		'VARIABLE' => '([\w\.\-]+)',
	);
	static $params_template_tags = array(
		'SLASH_QUOTE' => '\\\\"',
		'QUOTE' => '"',
		'EQUAL' => '=',
	);
	static $input_types = array();
	function __construct($code, $start_loop_index = 1, $loops = array())
	{
		$this->start_loop_index = $start_loop_index;
		$this->code = $code;
		$this->loops = $loops;
	}
	static function input_types($file_input_types = 'lib/form/input-types.xml')
	{
		if(!isset(CFormLayout::$file_input_types[$file_input_types]))
		{
			CFormLayout::$file_input_types[$file_input_types] = 1;
			CFormLayout::$input_types = CFormLayout::$input_types + CList(CXml(CFormLayout(file_get_contents($file_input_types))->parse())->decode())->extract_id('name');
		}
	}
	function parse()
	{
		require_once 'lib/utils/CTranslation.php';
		require_once 'lib/utils/CToken.php';
		$this->tokens = CTranslation($this->code, CFormLayout::$layout_tags)->tokenize();
		$pos = 0;
		$this->line = 0;
		$this->loop_index = $this->start_loop_index;

		try
		{
			$result = $this->translate($pos);
			if(module())
			{
				$result = str_replace('</form','<input type="hidden" name="submit_form_id" value="<?php echo $params[\'mid\'] ?>"><input type="hidden" name="module_id" value="<?php echo $params[\'mid\'] ?>"></form', $result);
			}
		}
		catch(Exception $e)
		{
			echo 'Parse error, stop parsing, return false.'.$e->getMessage();
			Clog('SYSTEM','Parse error, stop parsing, return false.');
			$result = '';
		}
		if(DEBUG)
		{
			//CLog('PARSE_LAYOUT', $result);
		}
		return $result;
	}
	function translate(&$pos, $in_php = false)
	{
		$code = '';
		$last_in_php = $in_php;
		$end_token = false;
		$in_code_php = false;
		while(!$end_token and $pos < sizeof($this->tokens))
		{

			$token = CToken($this->tokens[$pos]);
			switch($token->type)
			{
			case 'TEXT':
				if($in_php)
				{
					if(trim($token->param))
					{
						if(!$in_code_php)
						{
							$code .= '?>';
							$in_php = false;
						}
						$code .= $token->param;
					}
				}
				else
				{
					$code .= $token->param;
				}
				break;
			case 'BEGIN_PHP':
				$code .= '<?php ';
				$in_code_php = true;
				$in_php = true;
				break;
			case 'END_PHP':
				$code .= ' ?>';
				$in_php = false;
				$in_code_php = false;
				break;
			case 'BEGIN_LIST':
				$pos++;
				if(CToken($this->tokens[$pos])->type != 'TEXT')
				{
					throw new Exception('expression expected!');
				}
				if(CToken($this->tokens[$pos+1])->type != 'CLOSE_TAG')
				{
					throw new Exception(')--> expected!');
				}
				$this->loops[CToken($this->tokens[$pos])->param] = $this->loop_index;
				$for_var = $this->translate_variable(CToken($this->tokens[$pos])->param);
				$code .= ($in_php?'':'<?php').' $index'.$this->loop_index.' = 0;
'.((!strpos($for_var,'('))?'if(isset('.$for_var.'))':'').'
foreach('.$for_var.' as '.((!strpos($for_var,'('))?'&':'').'$item'.$this->loop_index.'){
$index'.$this->loop_index.'++;
';
				$pos++;
				$this->loop_index ++;
				$code .= $this->translate($pos, true);
				$code .= ' }'.($in_php?'':'?>');
				if(CToken($this->tokens[$pos])->type != 'END_LIST')
				{
					throw new Exception('<!--/LIST--> expected!');
					$pos++;
					return $code;
				}
				break;
			case 'BEGIN_FOR':
				$pos++;
				if(CToken($this->tokens[$pos])->type != 'TEXT')
				{
					throw new Exception('expression expected!');
				}
				if(CToken($this->tokens[$pos+1])->type != 'CLOSE_TAG')
				{
					throw new Exception(')--> expected!');
				}

				$code .= ($in_php?'':'<?php').'
for('.$this->translate_variable(CToken($this->tokens[$pos])->param).'){
';
				$pos++;
				$code .= $this->translate($pos, true);
				$code .= ' }'.($in_php?'':'?>');
				if(CToken($this->tokens[$pos])->type != 'END_FOR')
				{
					throw new Exception('<!--/FOR--> expected!');
					$pos++;
					return $code;
				}
				break;
			case 'BEGIN_SWITCH':
				$pos++;
				if(CToken($this->tokens[$pos])->type != 'TEXT')
				{
					throw new Exception('expression expected!');
				}
				if(CToken($this->tokens[$pos+1])->type != 'CLOSE_TAG')
				{
					throw new Exception(')--> expected!');
				}

				$code .= ($in_php?'':'<?php').'
switch('.$this->translate_variable(CToken($this->tokens[$pos])->param).'){
case time():
';
				$pos++;
				$code .= $this->translate($pos, true);
				$code .= ' }'.($in_php?'':'?>');
				if(CToken($this->tokens[$pos])->type != 'END_SWITCH')
				{
					throw new Exception('<!--/SWITCH--> expected!');
					$pos++;
					return $code;
				}
				break;
			case 'NEW_LINE':
				$this->line++;
				$code .= "\n\r";
				break;
			case 'ELSE':
				$code .= ($in_php?'':'<?php').' } else { '.($in_php?'':'?>');
				break;
			case 'DEFAULT':
				$code .= ($in_php?'':'<?php').' break; default:'.($in_php?'':'?>');
				break;
			case 'CASE':
				$pos++;
				if(CToken($this->tokens[$pos])->type != 'TEXT')
				{
					throw new Exception('expression expected!');
				}
				if(CToken($this->tokens[$pos+1])->type != 'CLOSE_TAG')
				{
					throw new Exception(')--> expected!');
				}
				$code .= ($in_php?'':'<?php').' break; ';
				$vars = explode('|', CToken($this->tokens[$pos])->param);
				foreach($vars as $var)
				{
					$code .= 'case '.$var.':';
					//$this->translate_variable($var).':';
				}
				$code .= ($in_php?'':'?>');
				$pos ++;
				break;
			case 'END_LIST':
			case 'END_IF':
			case 'END_FOR':
			case 'END_SWITCH':
			case 'END_INPUT':
				$end_token = true;
				break;
			case 'BEGIN_IF':
				$pos++;
				if(CToken($this->tokens[$pos])->type != 'TEXT')
				{
					throw new Exception('expression expected!');
				}
				if(CToken($this->tokens[$pos+1])->type != 'CLOSE_TAG')
				{
					throw new Exception(')--> expected!');
				}
				$code .= ($in_php?'':'<?php').' if('.$this->translate_variable(CToken($this->tokens[$pos])->param).'){
';
				$pos ++;
				$code .= $this->translate($pos, true);
				$code .= ' }'.($in_php?'':'?>');
				if(CToken($this->tokens[$pos])->type != 'END_IF')
				{
					throw new Exception('<!--/IF--> expected!');
					$pos++;
					return $code;
				}
				break;
			case 'LEFT_BRACKET':
				$code.='{';
				break;
			case 'RIGHT_BRACKET':
				$code.='}';
				break;
			case 'BEGIN_INPUT':
				CFormLayout::input_types();
				$item_name = 'item'.($this->loop_index-1);
				$params = $this->get_params($token->params(1));
				$$item_name = array(
					'name' => '',
					'id' => '',
					'params' => $params
				);
				$item = &$$item_name;
				if(isset($params['name']))
				{
					$item['name'] = $params['name'];
				}
				if(isset($params['id']))
				{
					$item['id'] = $params['id'];
				}

				$pos++;
				$this->loop_index++;
				$child_value = $this->translate($pos, false);
				$item['value'] = $child_value?1:0;
				if(CToken($this->tokens[$pos])->type != 'END_INPUT' or CToken($this->tokens[$pos])->params(0) != $token->params(0))
				{
					throw new Exception('</i:'.$token->params(0).'> expected!');
					$pos++;
					return $code;
				}
				if(isset(CFormLayout::$input_types[strtoupper($token->params(0))]))
				{
					$code .= ($in_php?'':'<?php ');

					$input = &CFormLayout::$input_types[strtoupper($token->params(0))];
					if(isset($input['params']))
					{
						foreach($input['params'] as $key => $value)
						{
							if(!isset($item['params'][$key]))
							{
								$item['params'][$key] = $value;
							}
						}
					}
					$code .= '$'.$item_name.' = '.var_export($item, true).';';
					if(isset($input['links']))
					{
						foreach($input['links'] as $link)
						{
							$code .= 'link("'.$link.'");';
						}
					}
					$code .= ' ?>'.str_replace('$item0', '$'.$item_name, str_replace('<?php echo $item0[\'value\'];?>', $child_value,$input['template'])).'<?php ';
					$code .= ($in_php?'':'?>');
				}
				$this->loop_index--;
				break;
			case 'END_INPUT':
				break;
			case 'EVAL_VARIABLE':
				$code .= ($in_php?'':'<?php').' eval(\' ?>\'.CFormLayout('.$this->translate_variable($token->param).','.($this->loop_index).','.var_export($this->loops,1).')->parse().\'<?php \');'.($in_php?'':'?>');
				break;
			case 'VARIABLE':
				$code .= ($in_php?'':'<?php').' echo '.$this->translate_variable($token->param).';'.($in_php?'':'?>');
				break;
			}
			if(!$end_token)
			{
				$pos++;
			}
		}
		if($in_php != $last_in_php)
		{
			if($last_in_php)
			{
				$code .= '<?php ';
			}
			else
			{
				$code .= '?>';
			}
		}
//		echo $code;
		return $code;
	}
	function get_params($text)
	{
		$params = array();
		$current_param = '';
		$state = 0;
		$tokens = CTranslation($text, CFormLayout::$params_template_tags)->tokenize();
		$custom_field = false;
		$i = 0;
		foreach($tokens as $token)
		{
			$token = CToken($token);
			$ok = false;
			switch($token->type)
			{
				case 'TEXT':
					if($state == 3)
					{
						$params[$current_param] .= $token->param;
						$ok = true;
					}
					elseif(trim($token->param))
					{
						if($state == 0)
						{
							$current_param = trim($token->param);
							$params[$current_param] = '';
							$state ++;
							$ok = true;
						}
					}
					break;
				case 'QUOTE':
					if($state == 2)
					{
						$state ++;
						$ok = true;
					}
					elseif($state == 3)
					{
						$state = 0;
						$ok = true;
					}
					break;
				case 'SLASH_QUOTE':
					if($state == 3)
					{
						$params[$current_param] .= '"';
						$ok = true;
					}
					break;
				case 'EQUAL':
					if($state == 1)
					{
						$state = 2;
						$ok = true;
					}
					elseif($state == 3)
					{
						$params[$current_param] .= '=';
						$ok = true;
					}
					break;
			}
			if(!$ok)
			{
				if($state == 1)
				{
					throw new Exception('= expected!');
				}
				elseif($state == 2)
				{
					throw new Exception('" expected!');
				}
				elseif($state == 0)
				{
					throw new Exception('identify expected!');
				}
			}
		}
		return $params;
	}
	function translate_variable($var)
	{
		$code = '';
		if(preg_match('/[^\w\.\-\#\[\]]/',$var) or strpos($var, '.['))
		{
			$tokens = CTranslation(str_replace('.[','%%',$var), CFormLayout::$layout_template_tags)->tokenize();
			$custom_field = false;
			$i = 0;
			foreach($tokens as $token)
			{
				$token = CToken($token);
				switch($token->type)
				{
					case 'TEXT':
						$code .= $token->param;
						break;
					case 'ARRAY':
						$code .= '[\''.$token->param.'\']';
						break;
					case 'STRING':
						if(sizeof($tokens)==1)
						{
							$code .= 'language(\''.$token->param.'\')';
						}
						else
						{
							$code .= '\''.$token->param.'\'';
						}
						break;
					case 'CUSTOM_FIELD':
						$custom_field = true;
						break;
					case 'METHOD_CALL':
						$code .= '->'.$token->param.'(';
						break;
					case 'CLASS_ATTRIBUTE':
						$code .= '->'.$token->param;
						break;
					case 'FUNCTION_CALL':
						$code .= $token->param.'(';
						break;
					case 'RIGHT_BRACE':
						if($custom_field)
						{
							$code .= ']';
						}
						break;
					case 'RIGHT_PARENTHESE':
						$code .= ')';
						break;
					case 'PHP_VARIABLE':
						$code .= $token->param;
						break;
					case 'CLASS_STATIC':
						$code .= $token->param.'::';
						break;
					case 'VARIABLE':
						if($i<sizeof($tokens)-1 and CToken($tokens[$i+1])->type == 'CUSTOM_FIELD')
						{
							$code .= str_replace('\'temp\']','',$this->translate_variable($token->param.'.temp'));

						}
						else
						{
							$code .= $this->translate_variable($token->param);
						}
						break;
				}
				$i++;
			}
		}
		else
		if(is_numeric($var) or !preg_match('/[^A-Z\_\-]/',$var))
		{
			$code .= $var;
		} else if ($var=='each') {
			// quang's
			$code = '$item'.($this->loop_index-1);
		} else {
			$vars = explode('.',$var);
			$index = array_pop($vars);
			if(strpos($index,'[')) {
				if($index[strlen($index)-1] != ']') {
					$this->error('] expected!');
				} else {
					$index = explode('[',substr($index,0, strlen($index)-1));
					$index = '[\''.$index[0].'\'][\''.$index[1].'\']';
				}
			} else {
				$index = '[\''.$index.'\']';
			}
			if(!sizeof($vars)) {
				$code = '$params'.$index.'';
			} else {
				if(join('.',$vars) == 'each') {
					if($index == '[\'_index\']') {
						$code = '$index'.($this->loop_index-1);
					} else {
						$code = '$item'.($this->loop_index-1).$index;
					}
				} else if(isset($this->loops[join('.',$vars)])) {
					if($index == '[\'_index\']') {
						$code = '$index'.$this->loops[join('.',$vars)];
					} else if ($index!='') {
						$code = '$item'.$this->loops[join('.',$vars)].$index;
					} else {
						$code = '$item'.$this->loops_index-1;
					}
				} else {
					if($index == '[\'_index\']') {
						$code = '$index'.($this->start_loop_index + sizeof($vars) - 2);
					} else {
						$code = '$item'.($this->start_loop_index + sizeof($vars) - 2).$index;
					}
					//$this->error('Unknow list identifier '.$var);
				}
			}
		}
//		echo $code;
		return $code;
	}
}

/**
 * Enter description here...
 *
 * @param unknown_type $code
 * @param unknown_type $start_loop_index
 * @param unknown_type $loops
 * @return CFormLayout
 */
function CFormLayout($code, $start_loop_index = 1, $loops = array())
{
	return new CFormLayout($code, $start_loop_index, $loops);
}
function array2list($items)
{
	$new_items = array();
	foreach($items as $key=>$value)
	{
		$new_items[] = array('id'=>$key, 'value'=>$value);
	}
	return $new_items;
}
?>